<?php

require_once DCES_BASE_DIR . 'utils/Problem.php';

/**
 * Created by IDEA.
 * User: ilya
 * Date: 12.10.2010
 * Time: 0:40:42
 */

class ResultsUpdater {

    private $contest_id;
    private $contest_settings;
    private $id2problem = array();

    public function __construct($contest_id, ContestSpecificSettings $contest_settings) {
        $this->contest_id = $contest_id;
        $this->contest_settings = $contest_settings;
    }

    /**
     * Refresh results for contest
     * @static
     * @param  $contest_id int id of contest
     * @param  $refresh_all boolean asks to refresh all submissions for contest, not only that after the last check
     * @return boolean true if need refresh
     */
    public function refreshResults($refresh_all = false) {
        $contest_id = $this->contest_id;

        $prfx = DB_PREFIX;
        $row = Data::getRow(sprintf("
                SELECT `last_test_id`
                FROM ${prfx}contest
                WHERE id=%s",
            Data::quote_smart($contest_id)
        ));
        if (!$row)
            throwBusinessLogicError(14);

        $last_test_id = $row['last_test_id'];

        if ($refresh_all)
            $last_test_id = 0;

        //get last submission
        $last_submitted_row = Data::getRow(sprintf("
            SELECT MAX(test_id)
            FROM ${prfx}submission_history
            WHERE contest_id=%s
        ", Data::quote_smart($contest_id))
        );

        if (!$last_submitted_row)
            return;
        $last_submitted = $last_submitted_row[0];
        if (!$last_submitted)
            $last_submitted = 0;

        //return if nothing changed
        if ($last_submitted == $last_test_id)
            return;

        //get users and their problems

        $users_and_problems = Data::getRows(sprintf("
            SELECT DISTINCT `problem_id`, `user_id`
            FROM ${prfx}submission_history
            WHERE contest_id=%s AND test_id>$last_test_id AND test_id<=$last_submitted
            ", Data::quote_smart($contest_id)
        ));

        if (!$users_and_problems)
            return;

        //get all problems settings
        $all_problems = Data::getRows(sprintf("
            SELECT `id`, `contest_settings`
            FROM ${prfx}problem
            WHERE contest_id=%s
            ", Data::quote_smart($contest_id)
        ));

        while ($problem_row = Data::getNextRow($all_problems)) {
            $problem_settings = Data::_unserialize($problem_row['contest_settings']);
            //copy values from contest settings if needed
            foreach (array('resultChoice', 'keySorting', 'transferList', 'resultTransitionCode') as $a_setting) {
                if (!$problem_settings->$a_setting)
                    $problem_settings->$a_setting = $this->contest_settings->$a_setting;
            }
            $this->id2problem[$problem_row['id']] = $problem_settings;
        }

        //process all users and problems
        while ($user_and_problem = Data::getNextRow($users_and_problems)) {
            $user_id = $user_and_problem['user_id'];
            $problem_id = $user_and_problem['problem_id'];

            $result = $this->update_result($user_id, $problem_id);

            //save result in db
            $quoted_result = Data::quote_smart(serialize($result));
            Data::submitModificationQuery(sprintf("
                INSERT INTO ${prfx}result (user_id, problem_id, contest_id, result)
                VALUES (%s, %s, %s, %s)
                ON DUPLICATE KEY UPDATE result=%s
            ",
                Data::quote_smart($user_id),
                Data::quote_smart($problem_id),
                Data::quote_smart($contest_id),
                $quoted_result,
                $quoted_result
            ));
        }

        //update last tested id
        Data::submitModificationQuery(
            sprintf(
                "UPDATE ${prfx}contest SET last_test_id=%s WHERE id=%s",
                Data::quote_smart($last_submitted),
                Data::quote_smart($contest_id)
            )
        );

        Data::execPendingQueries();
    }

    private function get_solution_from_history_row($submission, $num) {
        $res = Data::_unserialize($submission['result']);
        $res['_time'] = DateMySQLToPHP($submission['submission_time']);
        $res['_num'] = $num;
        return $res;
    }

    private function update_result($user_id, $problem_id) {

        /** @var ContestSpecificSettings $problem_settings */
        $problem_settings = $this->id2problem[$problem_id];

        $prfx = DB_PREFIX;

        $submissions = Data::getRows("
            SELECT result, submission_time
            FROM ${prfx}submission_history
            WHERE user_id=$user_id AND problem_id=$problem_id
            ORDER BY test_id DESC
        ");

        $num = Data::getRowsCount($submissions);

        $last_submission_row = Data::getNextRow($submissions);
        $last_solution = $this->get_solution_from_history_row($last_submission_row, $num);

        $best_solution = $last_solution;

        $keySorting = $problem_settings->keySorting;

        while ($submission = Data::getNextRow($submissions)) {
            $num--;
            $solution = $this->get_solution_from_history_row($submission, $num);

            //compare solution with best solution
            if ($this->isBetter($solution, $best_solution, $keySorting))
                $best_solution = $solution;
        }

        if ($problem_settings->resultChoice === 'Best')
            $solution = $best_solution;
        else
            $solution = $last_solution;

        $transferList = $problem_settings->transferList;
        $resultTransitionCode = $problem_settings->resultTransitionCode;

        $transferList_isEmpty = !$transferList || count($transferList) === 0 || count($transferList) === 1 && !$transferList[0];
        $resultTransitionCode_isEmpty = !$resultTransitionCode || !trim($resultTransitionCode);

        $result = array();
        if ($transferList_isEmpty) {
            //transfer all (except _num and _time) if there is no transition code
            if ($resultTransitionCode_isEmpty)
                foreach (array_keys($solution) as $key) {
                    if ($key != '_num' && $key != '_time')
                        $result[$key] = $solution[$key];
                }
        } else {
            foreach ($transferList as $key)
                $result[$key] = $solution[$key];
        }

        //run code if exist
        if (!$resultTransitionCode_isEmpty) {
            eval($resultTransitionCode);
        }

        return $result;
    }

    private function isBetter($solution1, $solution2, $keySorting) {
        if (!$keySorting || count($keySorting) == 0)
            return false;
        foreach ($keySorting as $key) {

            if (!array_key_exists($key, $solution1) || !array_key_exists($key, $solution2))
                return false;

            $v1 = $solution1[$key];
            $v2 = $solution2[$key];

            if (strpos($key, '-') === 0) { //descending
                if ($v1 < $v2)
                    return true;
                else if ($v1 > $v2)
                    return false;
            } else { //ascending
                if ($v1 > $v2)
                    return true;
                else if ($v1 < $v2)
                    return false;
            }
        }

        return false;
    }

}